package com.uxsino.simo.indicator;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;

import org.apache.commons.lang3.tuple.Triple;

/**
 * aggregate on ListIndicator's value which is List<Map<String,Object>> to Map<String,Object>
 * using {@link AGGREGATE_FUNCTION}
 * 
 * 
 */
public class ListAggregateFunction implements Function<List<Map<String, Object>>, Map<String, Object>> {

    private static ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");

    /**
     *  srcFieldName, <resultFieldName,function>
     */
    private List<Triple<String, String, AGGREGATE_FUNCTION>> groupBys = new ArrayList<>();

    /**
     * add a field to aggregate on
     * @param srcField field name in source indicator value
     * @param resultField field name in result indicator value
     * @param func aggregate type see {@link AGGREGATE_FUNCTION}
     */

    public void addField(String srcField, String resultField, AGGREGATE_FUNCTION func) {

        // if (((CompoundIndicator) srcIndicator.getItemIndicator()).getField(srcField) == null) {
        // throw new NameNotFoundException("field named " + srcField + "not found");
        // }
        // if (resultIndicator.getField(srcField) == null) {
        // throw new NameNotFoundException("field named " + resultField + "not found");
        // }

        groupBys.add(Triple.of(srcField, resultField, func));
    }

    @Override
    public Map<String, Object> apply(List<Map<String, Object>> srcValueList) {
        HashMap<String, Object> result = new HashMap<>();
        if (srcValueList == null) {
            return result;
        }
        for (Triple<String, String, AGGREGATE_FUNCTION> entry : groupBys) {
            Double x = null;
            switch (entry.getRight()) {
            case SUM:
                x = listSum(srcValueList, entry.getLeft());
                break;
            case AVG:
                x = listAvg(srcValueList, entry.getLeft());
                break;

            case MAX:
                x = listMax(srcValueList, entry.getLeft());
                break;

            case MIN:
                x = listMin(srcValueList, entry.getLeft());
                break;
            case COUNT:
                x = (double) listCount(srcValueList, entry.getLeft());
                break;
            case COUNT_ALL:
                x = (double) listCountAll(srcValueList, entry.getLeft());
                break;
            case CUSTOM:
                x = (double) calcByCustom(srcValueList, entry.getLeft());
                break;
            }
            result.put(entry.getMiddle(), x);
        }

        return result;
    }

    /*********** static helpers *****************/

    public static Double listSum(List<Map<String, Object>> l, String fieldName) {
        Double r = null;

        for (Map<String, Object> row : l) {
            Object o = row.get(fieldName);
            if (o != null) {
                if (r == null) {
                    r = 0.0;
                }
                r += Double.parseDouble(o.toString());
            }
        }
        return r;
    }

    public static Double listAvg(List<Map<String, Object>> l, String fieldName) throws ArithmeticException {
        Double r = null;
        int rowCount = 0;
        for (Map<String, Object> row : l) {
            Object o = row.get(fieldName);
            if (o != null) {
                if (r == null) {
                    r = 0.0;
                }
                r += Double.parseDouble(o.toString());
                rowCount++;
            }
        }

        return r == null ? null : r / rowCount;
    }

    public static Double listMax(List<Map<String, Object>> l, String fieldName) {
        Double r = null;

        for (Map<String, Object> row : l) {
            Object o = row.get(fieldName);
            if (o != null) {
                double x = Double.parseDouble(o.toString());
                if (r == null || r < x)
                    r = x;
            }
        }
        return r;
    }

    public static Double listMin(List<Map<String, Object>> l, String fieldName) {
        Double r = null;

        for (Map<String, Object> row : l) {
            Object o = row.get(fieldName);
            if (o != null) {
                double x = Double.parseDouble(o.toString());
                if (r == null || r > x)
                    r = x;
            }
        }
        return r;
    }

    public static int listCount(List<Map<String, Object>> l, String fieldName) {
        int r = 0;

        for (Map<String, Object> row : l) {
            Object o = row.get(fieldName);
            if (o != null) {
                r++;
            }
        }
        return r;
    }

    public static int listCountAll(List<Map<String, Object>> l, String fieldName) {

        return l.size();
    }

    public static double calcByCustom(List<Map<String, Object>> l, String expr) {
        double result = 0;
        try {
            Map<String, Double> valueMap = new HashMap<>();
            for (Map<String, Object> ele : l) {
                for (String k : ele.keySet()) {
                    Double d = valueMap.getOrDefault(k, 0.0);
                    Object v = ele.get(k);
                    if (v instanceof Number) {
                        valueMap.put(k, d + Double.valueOf(v + ""));
                    }
                }
            }
            for (Entry<String, Double> entry : valueMap.entrySet()) {
                String key = entry.getKey();
                if (expr.contains(key)) {
                    expr = expr.replace(key, "" + entry.getValue());
                }
            }
            result = Double.valueOf(engine.eval(expr).toString());
        } catch (Exception e) {

        }
        return result;
    }

}
